package hell.satan.droidbdf.utils;

import android.support.annotation.NonNull;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 信息摘要工具类
 * Message Digest Algorithm Utils
 *
 * @author Cerberus
 */
@SuppressWarnings("unused")
public class DigestUtils {
    public static final String TAG = DigestUtils.class.getSimpleName();

    protected static final char[] CH_HEXS = "0123456789abcdef".toCharArray();
    protected static final long SZ_BUFFER = Constants.SIZE_KB;

    public static final String ALGORITHM_MD5 = "md5";
    public static final String ALGORITHM_SHA1 = "sha1";

    protected static String hex2str(@NonNull byte[] input) {
        StringBuilder sb = new StringBuilder();
        for (byte bt : input) {
            sb.append(CH_HEXS[(bt & 0xf0) >> 4]);
            sb.append(CH_HEXS[(bt & 0x0f)]);
        }
        return sb.toString();
    }

    /**
     * 信息摘要算法，计算字节数组哈希，仅供内部使用
     * Message Digest Algorithm, compute bytes hash, for internal use only
     *
     * @param algorithm 算法名称
     *                  The name of the algorithm to use
     * @param input     将要计算哈希的字节数组
     *                  The byte array
     * @return 计算出的单向哈希值 The computed one way hash value
     * @throws NoSuchAlgorithmException
     */
    protected static byte[] rawMDAInner(@NonNull String algorithm, @NonNull byte[] input)
            throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance(algorithm);
        return md.digest(input);
    }

    /**
     * 信息摘要算法，计算字节数组哈希，仅供内部使用
     * Message Digest Algorithm, compute bytes hash, for internal use only
     *
     * @param algorithm 算法名称
     *                  The name of the algorithm to use
     * @param input     将要计算哈希的文件
     *                  The File to compute hash
     * @return 计算出的单向哈希值 The computed one way hash value
     * @throws NoSuchAlgorithmException
     */
    protected static byte[] rawMDAInner(@NonNull String algorithm, @NonNull File input)
            throws NoSuchAlgorithmException {
        FileInputStream fis = null;
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            fis = new FileInputStream(input);
            int count;
            byte[] buffer = new byte[(int) SZ_BUFFER];
            while ((count = fis.read(buffer)) > 0) {
                md.update(buffer, 0, count);
            }
            return md.digest();
        } catch (IOException ex) {
            throw new IllegalArgumentException(ex);
        } finally {
            IOUtils.closeQuietly(fis);
        }
    }

    /**
     * 信息摘要算法，计算字节数组哈希
     * Message Digest Algorithm, compute bytes hash
     *
     * @param algorithm 算法名称
     *                  The name of the algorithm to use
     * @param input     将要计算哈希的字节数组
     *                  The byte array
     * @return 计算出的单向哈希值 The computed one way hash value
     * @throws NoSuchAlgorithmException
     */
    public static byte[] rawMDA(@NonNull String algorithm, @NonNull byte[] input)
            throws NoSuchAlgorithmException {
        return rawMDAInner(algorithm, input);
    }

    /**
     * 信息摘要算法，计算字符串哈希
     * Message Digest Algorithm, compute String hash
     *
     * @param algorithm 算法名称
     *                  The name of the algorithm to use
     * @param input     将要计算哈希的字符串
     *                  The String to compute hash
     * @param charset   字符编码名称
     *                  Charset name for encoding
     * @return 计算出的单向哈希值 The computed one way hash value
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public static byte[] rawMDA(@NonNull String algorithm, @NonNull String input, @NonNull String charset)
            throws NoSuchAlgorithmException, UnsupportedEncodingException {
        return rawMDAInner(algorithm, input.getBytes(charset));
    }

    /**
     * 信息摘要算法，计算字符串哈希
     * Message Digest Algorithm, compute String hash
     *
     * @param algorithm 算法名称
     *                  The name of the algorithm to use
     * @param input     将要计算哈希的文件
     *                  The File to compute hash
     * @return 计算出的单向哈希值 The computed one way hash value
     * @throws NoSuchAlgorithmException
     */
    public static byte[] rawMDA(@NonNull String algorithm, @NonNull File input)
            throws NoSuchAlgorithmException {
        return rawMDAInner(algorithm, input);
    }

    /**
     * 计算字节数组md5值
     * Compute byte array md5 hash
     *
     * @see #rawMDA(String, byte[])
     */
    public static byte[] rawMD5(@NonNull byte[] input) {
        try {
            return rawMDA(ALGORITHM_MD5, input);
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 计算字符串md5值
     * Compute String md5 hash
     *
     * @throws UnsupportedEncodingException
     * @see #rawMDA(String, String, String)
     */
    public static byte[] rawMD5(@NonNull String input, @NonNull String charset)
            throws UnsupportedEncodingException {
        return rawMD5(input.getBytes(charset));
    }

    /**
     * 计算字符串md5值
     * Compute String md5 hash
     *
     * @see #rawMD5(String)
     */
    public static byte[] rawMD5(@NonNull String input) {
        try {
            return rawMD5(input, Constants.CHARSET);
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 计算文件串md5值
     * Compute File md5 hash
     *
     * @see #rawMDA(String, File)
     */
    public static byte[] rawMD5(@NonNull File input) {
        try {
            return rawMDAInner(ALGORITHM_MD5, input);
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 计算字节数组sha1值
     * Compute byte array sha1 hash
     *
     * @see #rawMDA(String, byte[])
     */
    public static byte[] rawSHA1(@NonNull byte[] input) {
        try {
            return rawMDA(ALGORITHM_SHA1, input);
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 计算字符串sha1值
     * Compute String sha1 hash
     *
     * @throws UnsupportedEncodingException
     * @see #rawMDA(String, String, String)
     */
    public static byte[] rawSHA1(@NonNull String input, @NonNull String charset)
            throws UnsupportedEncodingException {
        return rawSHA1(input.getBytes(charset));
    }

    /**
     * 计算文件串sha1值
     * Compute File sha1 hash
     *
     * @see #rawMDA(String, File)
     */
    public static byte[] rawSHA1(@NonNull String input) {
        try {
            return rawSHA1(input, Constants.CHARSET);
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 计算文件串sha1值
     * Compute File sha1 hash
     *
     * @see #rawMDA(String, File)
     */
    public static byte[] rawSHA1(@NonNull File input) {
        try {
            return rawMDAInner(ALGORITHM_SHA1, input);
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * 计算字节数组md5值，并将结果转换为字符串
     * Compute byte array md5 hash, and convert the result to string
     *
     * @see #rawMD5(byte[])
     */
    public static String MD5(@NonNull byte[] input) {
        return hex2str(rawMD5(input));
    }

    /**
     * 计算字符串md5值，并将结果转换为字符串，如果编码不支持，则返回默认32个0
     * Compute String md5 hash, and convert the result to string, if charset not supported,
     * default value thirty-two '0' returned
     *
     * @see #rawMD5(String, String)
     */
    public static String MD5(@NonNull String input, @NonNull String charset) {
        try {
            return hex2str(rawMD5(input, charset));
        } catch (UnsupportedEncodingException ex) {
            LogWrapper.w(TAG, ex);
        }
        return "00000000000000000000000000000000";
    }

    /**
     * 计算字符串md5值，并将结果转换为字符串
     * Compute String md5 hash, and convert the result to string
     *
     * @see #rawMD5(String)
     */
    public static String MD5(@NonNull String input) {
        return MD5(input, Constants.CHARSET);
    }

    /**
     * 计算文件md5值，并将结果转换为字符串
     * Compute File md5 hash, and convert the result to string
     *
     * @see #rawMD5(File)
     */
    public static String MD5(@NonNull File input) {
        return hex2str(rawMD5(input));
    }

    /**
     * 计算字节数组sha1值，并将结果转换为字符串
     * Compute byte array sha1 hash, and convert the result to string
     *
     * @see #rawSHA1(byte[])
     */
    public static String SHA1(@NonNull byte[] input) {
        return hex2str(rawSHA1(input));
    }

    /**
     * 计算字符串sha1值，并将结果转换为字符串，如果编码不支持，则返回默认40个0
     * Compute String sha1 hash, and convert the result to string, if charset not supported,
     * default value forty '0' returned
     *
     * @see #rawSHA1(String, String)
     */
    public static String SHA1(@NonNull String input, @NonNull String charset) {
        try {
            return hex2str(rawSHA1(input, charset));
        } catch (UnsupportedEncodingException ex) {
            LogWrapper.w(TAG, ex);
        }
        return "0000000000000000000000000000000000000000";
    }

    /**
     * 计算字符串sha1值，并将结果转换为字符串
     * Compute String sha1 hash, and convert the result to string
     *
     * @see #rawSHA1(String)
     */
    public static String SHA1(@NonNull String input) {
        return SHA1(input, Constants.CHARSET);
    }

    /**
     * 计算文件sha1值，并将结果转换为字符串
     * Compute File sha1 hash, and convert the result to string
     *
     * @see #rawSHA1(File)
     */
    public static String SHA1(@NonNull File input) {
        return hex2str(rawSHA1(input));
    }
}
